---
title: MSMQ transaction management
summary: Starts and ends a MessageQueueTransaction used to batch one or more distributeDocument calls into a single transactional unit of work.
status: draft
owner: SAIF Documentation
last_reviewed: 2026-09-01
library: SAIF.Services.Printing
reference_type: embedded
operation_id: op-05-msmq-transaction-management
trigger: StartMQTransaction / EndMQTransaction invoked by caller
---

# MSMQ transaction management

Provides `StartMQTransaction` and `EndMQTransaction` so a caller can batch multiple [Distribute a document](op-04-distribute-a-document.md) calls into a single transactional unit of work against the MSMQ transactional queue, rather than committing a transaction per message.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-05 |
| **Trigger** | `StartMQTransaction` / `EndMQTransaction` invoked directly by a caller |
| **Inputs** | None (`StartMQTransaction`); `inAction As Boolean` commit/abort flag (`EndMQTransaction`) |
| **Outputs** | A committed or aborted `MessageQueueTransaction` |
| **Primary components** | `clsPrint.StartMQTransaction`, `clsPrint.EndMQTransaction` |
| **Entry point** | `SAIF.Services.Printing/clsPrint.vb` |

## Steps

1. **`StartMQTransaction`:** if a transaction is not already started (`m_TxnStarted = False`), creates a new `MessageQueueTransaction`, begins it, and sets `m_TxnStarted = True`.
   - If a transaction is already started, this call is a no-op — calling it twice in a row silently keeps the first transaction rather than starting a second one or raising an error. This idempotent-restart behavior underlies the "if the caller did not start one" framing in [BR-PRT-004](../business-rules.md#br-prt-004-transactional-msmq-send).
2. **`EndMQTransaction(inAction)`:** if `m_TxnStarted` is `True`, commits the transaction when `inAction = True`, or aborts it when `inAction = False`.
   - On any exception during commit or abort, the `Finally` block described in step 3 still executes.
3. **`Finally` (always runs):** resets `m_TxnStarted = False` and disposes/nulls `m_Txn`, regardless of whether a transaction was actually started or whether the commit/abort succeeded.
   - If `EndMQTransaction` is called when `m_TxnStarted = False`, the commit/abort branch in step 2 is skipped entirely, but this `Finally` cleanup still runs harmlessly.

## Process flow

```mermaid
flowchart TD
	subgraph Start_flow["StartMQTransaction"]
		S1(["StartMQTransaction called"]) --> S2{"m_TxnStarted?"}
		S2 -->|No| S3["New MessageQueueTransaction,<br>Begin, set flag True"]
		S2 -->|Yes| S4(["No-op"])
	end
	subgraph End_flow["EndMQTransaction(inAction)"]
		E1(["EndMQTransaction called"]) --> E2{"m_TxnStarted?"}
		E2 -->|Yes| E3{"inAction?"}
		E3 -->|True| E4["Commit"]
		E3 -->|False| E5["Abort"]
		E4 --> E6["Finally: reset flag,<br>dispose/null txn"]
		E5 --> E6
		E2 -->|No| E6
	end
```

## Failure and edge-case behavior
- There is no exception handling around `Commit()`/`Abort()` beyond the `Finally` cleanup — an exception raised from `Commit()` propagates to the caller after the `Finally` block has already reset state.
- Calling `StartMQTransaction` twice without an intervening `EndMQTransaction` silently reuses the existing transaction rather than erroring or starting a second one.
- [Distribute a document](op-04-distribute-a-document.md) relies on this same start/commit/rollback mechanic internally when the caller has not started a transaction of their own, per [BR-PRT-004](../business-rules.md#br-prt-004-transactional-msmq-send) and [BR-PRT-005](../business-rules.md#br-prt-005-msmq-failure-rollback).

## Related documentation
- [Index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Dependencies](../dependencies.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Configuration](../configuration.md)
- [API reference](../api/index.md)

## Sources
- `SAIF.Services.Printing/clsPrint.vb`
